var gameover;
var Game_state; // game state as a string of .-separated key-value pairs

var Token; // array of recognized words in command
var rawc;

var hist;
var hIndex;

var ccount;
var counters; // string to hold names of functions which "subscribe" to new command ticks

var debugging = true;
var debug1 = true;

function exec(x) 
{
    if ( x.indexOf("[[") == 0 && x.lastIndexOf("]]") + 2 == x.length ) {
        var cmd = x.substring(2, x.length-2);
        var ecmd = eval(cmd);
        if (ecmd != undefined && (ecmd + "").length > 0)
            say(ecmd);
    }
    else
        say(x);
}

function say(t) 
{
    if (t.length < 1) return;

    var SCROLL_INC = 300;
    var HTMLOut = document.getElementById("outDiv").innerHTML;

    // allow nested expressions in all say() strings
    if( t.indexOf("[[") != -1 )
    {
        var fullt = t;

        var openC = fullt.indexOf("[[") ;
        var closeC = fullt.indexOf("]]") ;

        t = fullt.substring(0, openC);
        var expr = eval( fullt.substring(openC+2, closeC) );
        t += (expr != undefined && (typeof expr) == "string") ? expr: "";
        // t += eval( fullt.substring(openC+2, closeC) );
        t += fullt.substring(closeC+2, fullt.length);
    }

    t = t.split("\n").join("<br>");
    t = t.split("[i").join("<i>");
    t = t.split("i]").join("</i>");
    //if (debug1 == false) t = t.split("_").join(" ");
    
    HTMLOut += t + "<br>";
    document.getElementById("outDiv").innerHTML = HTMLOut;

    document.getElementById("outDiv").scrollTop += SCROLL_INC;
    
    document.getElementById("textIn").focus();
}

function dsay(t) 
{
    if (debugging) {
        debug1 = true;    
        say("--- " + t);
        debug1 = false;
    }
}

function upKey() {
    if (hIndex > 0)
        hIndex--;
    document.getElementById("textIn").value = hist[hIndex];
}

function downKey() {
    var cmd = "";
    if (hIndex < hist.length - 1) {
        hIndex++;
        cmd = hist[hIndex];
    }
    document.getElementById("textIn").value = cmd;        
}

function parse(c)
{
    c = c.trim();
    c = c.split("\n").join("");
    c = c.split("[[").join("");
    c = c.split("]]").join("");

    hist[hIndex] = c;
    hIndex++;
    
    // print command
    say("\n" + colour("> " + c, ECHO_COLOUR));
    rawc = c;
    ccount++;
    
    // debug stuffs
if (c == "pgs") { dsay( Game_state ); return; }
if (c == "pcc") { dsay( ccount ); return; }
if (c == "lsc") { dsay( counters ); return; }
if (c.indexOf("sgs1") == 0 && debugging) {
    var state = c.substring(5, c.length);
    Game_state = state;
    look();
    return;
}

    c = c.toLowerCase();    

    Token = Token.slice(0, 0); // clear token array

// LOOP THROUGH COMMAND, FIND MEANINGFUL WORDS
    var i = 0;
    var done = false;
    while (!done && i < MAX_TOKENS)
    {
        var word = ""; // SET CURRENT TOKEN 
        if (c.indexOf(" ") == -1) // consider if command is only a single word
        {
            word = c;
            done = true;
        }
        else
        {
            word = c.substring(0, c.indexOf(" ")); // first word becomes current token
            c = c.substring(c.indexOf(" ") + 1, c.length); // trim first word from command
        }

    // CHECK IF CURRENT WORD IS A RECOGNIZED WORD
        var sensical = false;
        for(var j = 0; j < NUM_SYNS; j++) {
            if (word != "")
            {
                // check for phrases
                if (Synonyms[j].indexOf("." + word + "_") != -1) {
                
                    var c_ = word + "_" + c.split(" ").join("_");
                    
                    var synString = Synonyms[j].substring(Synonyms[j].indexOf("." + word + "_") + 1, 
                        Synonyms[j].length);
                        
                    while (synString.indexOf(word + "_") != -1) {
                
                        var phrase = synString.substring(0, synString.indexOf("."));
                        
                        if ( c_.indexOf(phrase) != -1 && 
                            ( c_.charAt(phrase.length) == "_" || 
                              c_.length == phrase.length) ) {
                        
                            sensical = true;
                            // word = phrase;
                            word = Synonyms[j].substring(0, Synonyms[j].indexOf("."));
                            c = c_.substring(phrase.length, c_.length).split("_").join(" ");
                            if (c.trim().length < 1) done = true;
                            
                            break;
                        }
                        
                        synString = synString.substring(phrase.length + 1, synString.length);
                    }
                }
                
                if (!sensical && Synonyms[j].indexOf("." + word + ".") != -1) {
                    sensical = true;
                    word = Synonyms[j].substring(0, Synonyms[j].indexOf("."));
                }
            }
        }
    
    // REMOVE MEANINGLESS/REDUNDANT WORDS
        // ignore repeated word
        if( i > 0 && word == Token[i-1] ) sensical = false;

        // ignore "up" after "pick"
        if( i > 0 && word == "up" && Token[i-1] == "take" ) sensical = false;

        // ignore "down" after "put"
        else if( i > 0 && word == "down" && Token[i-1] == "drop" ) sensical = false;

        // ignore "up" after "wake"
        else if( i > 0 && word == "up" && Token[i-1] == "wake" ) sensical = false;

        // change "put on" to "wear"
        else if( i > 0 && word == "on" && Token[i-1] == "drop" ) {
            Token[i-1] = "wear";
            sensical = false; }

        // change "put X on" to "wear X"
        else if( i > 1 && word == "on" && Token[i-2] == "drop" ) {
            Token[i-2] = "wear";
            sensical = false; }

        // change "take off" to "remove"
        else if( i > 0 && word == "off" && Token[i-1] == "take" ) {
            Token[i-1] = "unwear";
            sensical = false; }

        // change "take X off" to "remove X"
        else if (i > 1 && word == "off" && Token[i-2] == "take") {
            Token[i-2] = "unwear";
            sensical = false; }

        // change "look in" to "open"
        else if (i > 0 && word == "in" && Token[i-1] == "look") {
            Token[i-1] = "open";
            sensical = false; }

        // change "move DIRECTION" to "DIRECTION"
        else if(i > 0 && DIRECTIONS.indexOf(word + ".") != -1 && Token[i-1] == "move") {
            Token[i - 1] = word;
            sensical = false;
        }

    // IF IT'S SENSICAL, ADD CURRENT WORD TO TOKEN ARRAY
    // add anything as last word, to check for special sights/actions
        if (sensical || done)
        {
            // (except repeated words)
            if (Token.length < 1 || word != Token[i-1]) {
                Token[i] = word;
                i++;
            }
        }
    }
    
    if (Token[0] == "dir") {
        list_saves();
        return;
    } else if (Token[0] == "save") {
        save_game();
        return;
    } else if (Token[0] == "load") {
        load_save();
        return;
    } else if (Token[0] == "delete") {
        delete_save();
        return;
    } else if (Token[0] == "restart") {
        start();
        return;
    }

    obey();
    
    tick();
}

function tick()
{
    if (counters.length > 0 && counters.indexOf(".") != -1) {

        arrCounters = counters.split(".");
        for (i = 0; i < arrCounters.length; i++)
        {
            if (arrCounters[i].length > 1)
                eval(arrCounters[i] + "()");
        }
    }
}

function counterSubscribe(proc)
{
    if (counters.indexOf(proc) == -1) {
        counters += proc + ".";
        return true;
    }
    return false;
}

function counterUnsubscribe(proc)
{
    if (counters.indexOf(proc) != -1) {
        arrCounters = counters.split(".");
        counters = "";
        for (i = 0; i < arrCounters.length; i++)
        {
            if (arrCounters[i] != proc && arrCounters[i].length > 0)
                counters += arrCounters[i] + ".";
        }
    }
}

function sgs(name, value)
{
    // find the named thing/place/char, and if it exists in state list, remove it
    var states = Game_state.split(".");
    for (var i = 0; i < states.length; i++)
        if (states[i].split("-")[0] == name)
        {
            states[i] = "";
        }

    // rebuild the state list, minus the removed item
    Game_state = states.join(".");
    
    if (value == 0)
        return;
    
    // remove any double dots
    Game_state = Game_state.replace( "..", "." );
    // if (Game_state.indexOf('..') != -1)
        // Game_state = Game_state.substring(0,Game_state.indexOf('..')) +
                // Game_state.substring(Game_state.indexOf('..') + 1, Game_state.length);

    // add the new state of the named thing/place/char to the end of the state list
    Game_state += "." + name + "-" + value;
}

function gs(name) // gets the state of the named thing/place/char
{
    var states = Game_state.split(".");
    for (var i = 0; i < states.length; i++)
        if (states[i].split("-")[0] == name)
        {
            return states[i].split("-")[1];
        }

    return 0;
}

function capitalize(txt)
{
    if( txt == "" ) return("");
    
    // handle "a"
    if (txt.toLowerCase().indexOf("a ") == 0)
        txt = "the " + txt.substring(2, txt.length);
    
    return( txt.charAt(0).toUpperCase() + txt.substring(1,txt.length) );
}

function heroloc()
{
    return( Place[gs("hl")] );
}

function set_heroloc(loc)
{
    if( Game_state.indexOf("hl") == 0)
    {
        Game_state = Game_state.substring( Game_state.indexOf("."), Game_state.length );
    }
    Game_state = "hl-" + loc.id + Game_state;
    
    if( loc.intro != "" && !visited(loc) )
    {
        exec( heroloc().intro + "\n" );
        sgs( "v_" + loc.id, 1 );
    }

    // exec( "<font color=\"" + LOC_COLOUR + "\">" + heroloc().title + "</font>" );
    say(colour(heroloc().title, LOC_COLOUR));

    look();
}

function trim(str) {
    return str.replace(/^\s+|\s+$/g,"");
}


function colour(text, colour) {
    return ("<font color=\"" + colour + "\">" + text + "</font>");
}

function strip_(text) {
    return text.split("_").join(" ");
}

function visited(loc)
{
    return ( gs("v_" + loc.id) != 0 );
}

function wearing(th)
{
    return ( gs("wrn_" + th.id) == 1 );
}

function wear(x)
{
    if (!isThing(x)) {
        say("You can't wear that.");
        return;
    }

    var th = eval(x);

    if (wearing(th)) {
        say("You're already wearing it.");
        return;
    }

    if (!is_in_inv(th)) { 
        say("You don't have it.");
        return;
    }

    if (!th.wearable) {
        say("You can't wear that.");
        return;
    }    
    
    if (th.uses.indexOf("wear::") != -1) {
        var uses = th.uses.split("/");
        for (var i = 0; i < uses.length; i++) {        
            if ( uses[i].split("::")[0] == "wear" ) {
                exec( uses[i].split("::")[1] );
                break;
            }
        }
    } else
        say("\nYou are now wearing " + th.fullname + ".");
    
    sgs("wrn_" + th.id, 1);
}

function unwear(x)
{
    if( !isThing(x) ) {
        say("You can't do that.");
        return;
    }

    var th = eval(x);

    if( !th.wearable ) {
        say("You can't do that.");
        return;
    }

    if( !wearing(th) ) {
        say("You're not wearing it.");
        return;
    }    

    if (th.uses.indexOf("unwear::") != -1) {
        var uses = th.uses.split("/");
        for( var i = 0; i < uses.length; i++ ) {        
            if( uses[i].split("::")[0] == "unwear" ) {
                exec( uses[i].split("::")[1] );
                break;
            }
        }
    } else
       say("You removed the " + th.name + ".");
    
    sgs("wrn_" + th.id, 0);
}

function charloc(ch)
{
    return Place[ gs("cl_" + ch.id) ];
}

function set_charloc(ch, loc)
{
    sgs("cl_" + ch.id, loc.id); 
}

function thingloc(th)
{
    return Place[gs("tl_" + th.id)];
}

function set_thingloc(th, loc)
{
    sgs("tl_" + th.id, loc.id);
}

function thingmod(th)
{
    return parseInt( gs("tm_" + th.id) );
}

function set_thingmod(th, tm)
{
    sgs("tm_" + th.id, tm);
}

function talkindex(ch)
{
    return parseInt( gs("ti_" + ch.id) );
}

function set_talkindex(ch, ti)
{
    sgs("ti_" + ch.id, ti);
}

function add_to_inv(th)
{
    set_thingloc(th, nowhere);
    sgs("I_" + th.id, 1);
}

function drop(x)
{
        if( !isThing(x) )
        {   say("You can't do that."); return; }

        var th = eval(x);

        if( th.uses.indexOf("drop::") != -1 )
        {
            var uses = th.uses.split("/");
            for( var i = 0; i < uses.length; i++ )
                if( uses[i].split("::")[0] == "drop" )
                    say( uses[i].split("::")[1] );

            return;
        }

    if( !is_in_inv(th) )
    {
       say("You haven't got it.");
       return(false);
    }

    if( Token[2] == "in" && Token[3] ) // "put X in Y"
    {
       say("That would be pointless.");
       return false;
    }

    sgs("I_" + th.id, 0);
        sgs("dr_" + th.id, 1);
    set_thingloc(th, heroloc());
    say("You dropped the " + th.name + ".");
}

function is_in_inv(th)
{
    if( gs("I_" + th.id) == 1 ) return true
    return false;
}

function list_inv()
{
    var inv_list = "";
        var count = 0;

    for ( var i = 0; i < NUM_THING; i++)
        if ( is_in_inv(Thing[i]) )
        {
            if( inv_list != "" )
            inv_list += ", ";

            var fullname = Thing[i].fullname;

            if( Thing[i].fullname.indexOf("/") != -1 )
                fullname = Thing[i].fullname.split("/")[ thingmod(Thing[i]) ];

            inv_list += fullname;
            count++;
        }

    if( count == 0 )
    {
        say('You are not carrying anything.');
        return;
    }

    if( count > 1 )
    {
        var last = inv_list.substring( inv_list.lastIndexOf(",")+1, inv_list.length );
        inv_list = inv_list.substring( 0, inv_list.lastIndexOf(",") );

        if( count > 2 )
            inv_list += ",";

        inv_list += " and " + last;
    }

    say("You are carrying " + inv_list + ".");
}

function obey()
{
  if (debugging) {
    var ss = "";
    for (var i = 0; i < Token.length; i++)
      ss += Token[i] + " ";
    
    dsay("obeying: " + ss);  
  }
    
    if (Token[0] == "inventory") {
        list_inv();
        return;
    }    

    // check for special actions in current location
    if( heroloc().special != "" ) {
        var special = heroloc().special.split("/");
        
        for( var i = 0; i < special.length; i++ )
        {
            // add pipes to match whole phrases only
            var keys = "|" + special[i].split("::")[0] + "|";
            var key1 = "|" + Token.join("_") + "|"; // try matching substituted words
            var key2 = "|" + rawc.split(" ").join("_") + "|"; // try matching raw command
            
            if (keys.indexOf(key1) != -1 || keys.indexOf(key2) != -1) {
                exec( special[i].split("::")[1] );
                return;
            }         
        }
    }   

    if ( Token[0] == "look" && Token.length == 1 ) { look(); return; }

    if ( Token[0] == "look" ) { look_at(Token[ Token.length-1 ]); return; }

    if ( Token[0] == "take" ) { take(Token[ Token.length-1 ], "default"); return; }

    if ( Token[0] == "wear" ) { wear(Token[ Token.length-1 ]); return; }
    
    if ( Token[0] == "unwear" ) { unwear(Token[ Token.length-1 ]); return; }
    
    if ( Token[0] == "talk" ) { talk_to(Token[1]); return; }
    
    // if( Token[0] == "lockpick" ) { lockpick(Token[1]); return; }

    if( Token[0] == "give" )
    {
        give_to(Token[1], Token[Token.length - 1]);
        return;
    }

    if( Token[0] == "move" || DIRECTIONS.indexOf(Token[0]) >= 0 || DIRECTIONS.indexOf(Token[1]) >= 0 )
        { move(); return; }    
        
    if ( Token[0] == "use" || (isThing(Token[1]) && eval(Token[1]).uses.length > 0 && 
        eval(Token[1]).uses.indexOf(Token[0]) != -1) ) {
        use();
        return;
    }
    
    if ( tryGenericActions() )
        return;

    say(NONSENSE);
}

function use()
{
    if (!isThing(eval(Token[1]))) {
        say(NONSENSE);
        return;
    }

    var verb = Token[0];
    var th = eval(Token[1]);
    var obj;
    
    if (Token.length > 2) {
        verb += "->" + Token[Token.length-1];
        
        if (isThing(Token[Token.length-1]) || isChar(Token[Token.length-1]))
            obj = eval(Token[Token.length-1]);
    }
    
    if ( !is_in_inv(th) || (obj != undefined && isThing(obj.name) && !is_in_in(obj)) ) {
        say("You don't have it.");
        return;
    } else if ( obj != undefined && isChar(obj.name) && charloc(obj) != heroloc() ) {
        say(capitalize(obj) + " isn't here.");
        return;
    }
    
    var uses = th.uses.split("/");
    for( var i = 0; i < uses.length; i++ )
    {
        if ( uses[i].split("::")[0] == verb )
        {
            exec( uses[i].split("::")[1] );
            return;
        }
    }
        
    if (Token.length < 3)
        say("Use it how?");
    else
        say("You can't use it like that.");
}

function move()
{
        if ( Token[0] == "move" && Token.length == 1 ) 
        {
            say("Where?");
            return;
        }

        var dest = Token[0]; // player's desired destination, which exists in the list of recognized words
        if( Token[0] == "move" ) dest = Token[1];

        if( DIRECTIONS.indexOf(dest) != -1 )
        {
            if( heroloc().exits.indexOf(dest+"::") < 0 && heroloc().hExits.indexOf(dest+"::") < 0 )
            {
                say("You can't see anywhere to go in that direction.");
                return;
            }
        }
        else if( heroloc().exits.indexOf("::"+dest) < 0 )
        {
            say("You can't get there from here.");
            return;
        }

        var exits = heroloc().exits.split(".");
        var hExits = heroloc().hExits.split(".");

        for( var i = 0; i < hExits.length; i++)
        {
            var exitAlias = hExits[i].split("::")[0];
            var realExit = hExits[i].split("::")[1];

            if( dest == exitAlias ) dest = realExit;
        }

        for( var i = 0; i < exits.length; i++)
        {
            var exitRoute = exits[i].split("::")[0];
            var exitDest = exits[i].split("::")[1];

            if( exitRoute == dest || exitDest == dest )
            {
                if( !can_go(eval(exitDest)) ) return;

                set_heroloc(eval(exitDest));
                return;
            }
        }

        say("Can't tell where you're trying to go.");
}

function look()
{
    exec( heroloc().description );

    list_chars();
    list_exits();
    //list_things();

}

function list_exits()
{
    var exitStr = "";
    var exitArr = heroloc().exits.split(".");
    
    if (exitArr.length == 1 && exitArr[0].length > 0) {
        exitStr = "An exit leads " + exitArr[0].split("::")[0] + ".";
    }
    else if (exitArr.length > 1) {
        exitStr = "Exits are ";
        for (var i = 0; i < exitArr.length; i++) {
        
            var exit = exitArr[i].split("::")[0]; 
            
            if (i < exitArr.length-1)
                exitStr += exitArr.length > 2 ? exit + ", " : exit + " ";
            else
                exitStr += "and " + exit + ".";
        }
    }    
    
    if (exitStr.length > 0)
        say( capitalize(exitStr) );
}

function list_chars()
{
        var chars = "";
        var states = Game_state.split(".");

        for( var i = 0; i < states.length; i++ )
        {
            if( states[i].split("-")[1] == heroloc().id.toString() && 
                states[i].split("-")[0].indexOf("cl_") == 0 )
            {
                var chID = parseInt( states[i].split("-")[0].split("_")[1] );
                chars += Char[chID].pname + "|";
            } 
        }
        
        if( chars.length > 0)
        {
            chars = chars.substring( 0, chars.length-1 );
            var chArr = chars.split("|");
            var chStr = "";
            
            if( chArr.length == 1 )
                chStr = chArr[0] + " is here.";
            else
            {
                for( var i = 0; i < chArr.length; i++ )
                {
                    if( i < chArr.length-1 )
                        chStr += chArr.length > 2 ? chArr[i] + ", " : chArr[i] + " ";
                    else
                        chStr += "and " + chArr[i] + " are here.";
                }
            }
            
            say( capitalize(chStr) );
        }
} 
/*
function list_things()
{
        var things = "";
        var states = Game_state.split(".");

        for( int i = 0; i < states.length; i++)
        {
            if( states[i].split("-")[1] == heroloc().id && states[i].split("-")[0].indexOf("tl_") == 0 )
            {
                var thID = parseInt( states[i].split("-")[0].split("_")[1] );
                things += Thing[thID].fullname + ".";
            }
        }

        if( things.length > 0)
        {
            var thArr = things.split(".");
            var thStr = "You can also see ";

            for( int i = 0; i < thArr.length; i++ )
            {
               if( thArr.length > 2 && i != thArr.length-1 )
                   thStr += thArr[i] + ", ";
               else if( thArr.length == 2 && i == thArr.length-1 )
                   thStr += " and " + thArr[i];
               else if( thArr.length == 1 )
                   thStr = thArr[i] + ".";
               else // if it's the first of only two things
                   thStr += thArr[i] + " ";
            }

            say( thStr );
        }
} */

function look_at(x)
{
    if (x == "self") {
        self_look();
        return;
    }
    
    if (x == "all") {
        look();
        return;
    }

    // if x is a sight viewable in the current room   
    var isXSight = heroloc().sights.indexOf(x + "::") != -1 || 
        heroloc().sights.indexOf(x + "|") != -1;
        
    // also check for last word of raw command
    var y = rawc.substring(rawc.lastIndexOf(" ")+1, rawc.length);
    var isYSight = heroloc().sights.indexOf(y + "::") != -1 || 
        heroloc().sights.indexOf(y + "|") != -1;

    if( isChar(x) )
    {
        var ch = eval(x);

        if( charloc(ch) != heroloc() ) {
            if (isXSight) {
                sightsee(x);
            }
            else {
                say("You can't see " + ch.pname+ " here.");
            }
        } else
            say( ch.description );
            
        return;
    }

    if( isThing(x) )
    {
        var th = eval(x);

        // if( thingloc(th) == heroloc() || is_in_inv(th) || wearing(th) ) {
        if (is_in_inv(th) || wearing(th)) {
            var desc = th.description;

            if ( th.description.indexOf("/") != -1 )
                desc = th.description.split("/")[ thingmod(th) ];

            if( wearing(th) ) desc += " You're currently wearing it.";

            say(desc);
        }
        else if (isXSight)
            sightsee(x);
        else
            say("You can't see it.");

        return;
    }
    
    if (isXSight) {
        sightsee(x);
        return;
    } else if (isYSight) {
        sightsee(y);
        return;
    }
    
    say("Nothing special.");
}

function sightsee(sight)
{
    var sights = heroloc().sights.split("/");

    for( var i = 0; i < sights.length; i++)
    {
        if( sights[i].split("::")[0].indexOf(sight) != -1 )
        {
            exec(sights[i].split("::")[1]);
            return;
        }
    }
    
    say("You can't see it.");
}

function take(thing, txt)
{
    var taken = false;
    var th = isThing(thing) ? eval(thing) : "";

    if( isChar(thing) && charloc(eval(thing)) == heroloc() )
        say("You're not in the business of kidnapping.");
    else if ( Token.length == 1 || thing === undefined || thing.length < 1 )
        say("What?");
    else if ( !isThing(thing) )
        say("You can't get that.");
    else if ( wearing(th) )
        say("You're wearing it.");
    else if ( is_in_inv(th) )
        say("You already have it.");
    else if ( thingloc(th) != heroloc() ) {
        say("You can't see it.");
        var loc = thingloc(th);
    }
    else {

        // if( th.uses.indexOf("take::") != -1 )
        // {
            // var uses = th.uses.split("/");
            // for( var i = 0; i < uses.length; i++ )
                // if( uses[i].split("::")[0] == "take" )
                   // say( uses[i].split("::")[1] );

            // return taken;
        // }
        
        add_to_inv(th);
        taken = true;
        
        if (txt == "default")
            txt = "You picked up the " + strip_(th.name) + ".";
        
        if (txt.length > 0)
            say(txt);
    }
        
    return taken;
}

function talk_to(x)
{ 
    if( x == "self" ) { say(SELF_TALK); return; }

    if( isThing(x) )
    {
        var th = eval(x);

        if( thingloc(th) != heroloc() && !is_in_inv(th) )
        {   say("You can't see it."); return; }
        else
        {   say("No response."); return; }
    }

    if( heroloc().sights.indexOf(x + "::") != -1 || heroloc().sights.indexOf(x + "|") != -1)
    {   say("No response."); return; }

    if( Token.length == 1 || !isChar(x) ) { say(TALK_NOONE); return; }

    var ch = eval(x);
    
    if( charloc(ch).id != heroloc().id )
    {   say( capitalize(ch.pname)+ " isn't here." ); return }
    
    
    if (!can_talk(ch)) return;

    if( Token.length > 2 )
    {
        var topic = Token[ Token.length-1 ];

        var topics = ch.subjects.split("/");
        
        for( var i = 0; i < topics.length; i++ )
        {
            if ( topics[i].indexOf(topic + "::") != -1 || topics[i].indexOf(topic + "|") != -1 ||
                (i == topics.length-1 && topics[i].split("::")[0] == "unknown") ) {
                exec( topics[i].split("::")[1] );
                return;
            }
        }
    }
    
    exec( ch.talks );
}
    

function give_to(x, y)
{
    var gift;
    var recipient;

    if (isThing(x) && isChar(y)) {
        gift = eval(x);
        recipient = eval(y);
    } else if (isChar(x) && isThing(y)) {
        gift = eval(y);
        recipient = eval(x);    
    } else if (x === undefined) {
        say("What are you trying to give away?");
        return;
    } else if (isChar(x) || isChar(y)) {
        say(capitalize(recipient.pname) + " isn't interested.");
        return;
    } else if (isThing(x) || isThing(y)) {
        say("Who are you trying to give it to?");
        return;
    } else {
        say("What?");
        return;
    }
    
    if (!is_in_inv(gift)) {
        say("You don't have it.");
        return;
    }
    
    if (charloc(recipient) != heroloc()) {
        say(capitalize(recipient.pname) + " isn't here.");
        return;
    }
    
    var presents = recipient.presents.split("/");
    
    for (var i = 0; i < presents.length; i++) {
        if (presents[i].split("::")[0] == gift.name) {
            exec(presents[i].split("::")[1]);
            return;
        }
    }
    
    say(capitalize(recipient.pname) + " isn't interested.");    
}

function isThing(x)
{
    for( var i = 0; i < NUM_THING; i++ ) {
        if (Thing[i].name == x)
            return true;
    }
    
    return false;
}

function isChar(x)
{
        for( var i = 0; i < NUM_CHAR; i++ ) 
        {
            if (Char[i].name == x) return true;
        }
        return false;
}

function isPlace(x)
{
        for( var i = 0; i < NUM_PLCS; i++ ) 
            if (Place[i].name == x) return true;
        return false;
}

/* save/load game */

function save_name()
{
    var name = rawc.substring(Token[0].length + 1, rawc.length).trim();
    if (name.length > 32)
        name = name.substring(0, 32);
    name = name.split(" ").join("_");
    return name;
}

function save_game()
{
    var name = save_name();

    if (name === undefined || name == "")
    {
        say("Please name your saved game: type \"save " + colour("name", SAVE_COLOUR) + "\".");
        return;
    }
    
    var date = new Date( new Date().setYear(1 + new Date().getFullYear()) );

    document.cookie = SAVEGAME_PREFIX + name + "=" + Game_state + ";expires=" + 
        date.toUTCString() + ";";
        
    say("Game saved: " + colour(name, SAVE_COLOUR) + ". Type \"load " + 
        name + "\" to continue from this point. Type \"dir\" to see a list \
        of your saved games.");
}

function list_saves()
{
    var list = "";
    
    var cookies = document.cookie.split("; ");
    for (var i = 0; i < cookies.length; i++) {
        if (cookies[i].split("=")[0].indexOf(SAVEGAME_PREFIX) == 0) {
            var save = cookies[i].split("=")[0].substring(SAVEGAME_PREFIX.length, cookies[i].split("=")[0].length);
            list += "\n    " + colour(save, SAVE_COLOUR);
        }
    }
    
    if (list.length < 1 )
        say("No saved games found.");
    else
        say("Saved games:" + list);
}

function load_save()
{
    var name = save_name();
    
    if (name === undefined || name == "")
    {
        say("What saved game are you trying to load? Type \"load " + 
            colour("name", SAVE_COLOUR) + "\".");
        return;
    }

    var cookies = document.cookie.split(";");
    for (var i = 0; i < cookies.length; i++)
    {
        if (cookies[i].indexOf(SAVEGAME_PREFIX + name + "=") != -1)
        {
            Game_state = cookies[i].split("=")[1];
            say("Restored game from " + colour(name, SAVE_COLOUR) + ".\n");
            set_heroloc(heroloc());
            return;
        }
    }
    
    say("Saved game " + colour(name, SAVE_COLOUR) + " not found. Type \"dir\" to see a list \
        of your saved games.");
}

function delete_save()
{
    var name = save_name();
    
    if (name === undefined || name == "")
    {
        say("What saved game are you trying to delete? Type \"delete " + colour("name", SAVE_COLOUR) + "\".");
        return;
    }
    
    if(!document.cookie)
    {
        say("No saved games found.");
        return;
    }

    var foundit = false;

    var cookies = document.cookie.split(";");
    for (var i = 0; i < cookies.length; i++)
    {
        if (cookies[i].indexOf(SAVEGAME_PREFIX + name + "=") != -1)
        {
            foundit = true;
            break;
        }
    }
    
    if (!foundit)
    {
        say("Saved game " + colour(name, SAVE_COLOUR) + " not found. Type \"dir\" to see a list \
            of your saved games.");
    } else {
        var expires = new Date();
        expires.setDate(expires.getDate() - 1);    
        document.cookie = SAVEGAME_PREFIX + name + "=;expires=" + expires.toUTCString();
        say("Saved game " + colour(name, SAVE_COLOUR) + " deleted. Type \"dir\" to see a list of \
            your saved games.");
    }
}

function start()
{
    gameover = false;
    Game_state = "";

    Token = new Array();
    rawc = "";

    hist = new Array();
    hIndex = 0;

    ccount = 0;
    counters = "";    
    
    document.getElementById('outDiv').innerHTML = "";
    document.getElementById("textIn").value = "";
    document.getElementById("textIn").focus();
        
    for (var i = 0; i < NUM_CHAR; i++)
       set_charloc(Char[i], Char[i].firstplace);
       
    for (var i = 0; i < NUM_THING; i++) {
       if( Thing[i].firstplace == "inv" ) add_to_inv( Thing[i] );
       else set_thingloc( Thing[i], Thing[i].firstplace );
    } 
    
    say(INTRO + "\n");

    set_heroloc(START_LOC);
}